﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using DomainDriver.CommonUtilities.Collections;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Events;
using DomainDriver.DomainModeling.Exceptions;
using DomainDriver.DomainModeling.Queries;
using DomainDriver.DomainModeling.StorageManagers;

namespace DomainDriver.DomainModeling.Repositories
{
    public partial class Repository<KEY, KEYED_DOMAIN_OBJECT> : IRepository<KEY, KEYED_DOMAIN_OBJECT>
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        private IStorageManager<KEY, KEYED_DOMAIN_OBJECT> m_StorageManager;

        public Repository(IStorageManager<KEY, KEYED_DOMAIN_OBJECT> storageManager)
        {
            m_StorageManager = storageManager;
        }

        #region IRepository Implementation

        public event AddingDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> AddingDomainObject;
        public event AddedDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> AddedDomainObject;
        public event RemovingDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> RemovingDomainObject;
        public event RemovedDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> RemovedDomainObject;
        public event UpdatingDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> UpdatingDomainObject;
        public event UpdatedDomainObjectDelegate<KEY, KEYED_DOMAIN_OBJECT> UpdatedDomainObject;

        public void Add(KEYED_DOMAIN_OBJECT domainObject)
        {
            try
            {
                AssertDomainObjectNotNull(domainObject);
                AssertDomainObjectDoesNotExistInRepository(domainObject);
                DispatchAddingEvent(domainObject);
                m_StorageManager.Add(domainObject);
                DispatchAddedEvent(domainObject);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }
        }

        public void Remove(KEY domainObjectKey)
        {
            try
            {
                KEYED_DOMAIN_OBJECT domainObject = m_StorageManager.Read(domainObjectKey);
                AssertDomainObjectNotNull(domainObject);
                DispatchRemovingEvent(domainObject);
                m_StorageManager.Remove(domainObject);
                DispatchRemovedEvent(domainObject);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }
        }

        public void Update(KEYED_DOMAIN_OBJECT domainObject)
        {
            try
            {
                AssertDomainObjectNotNull(domainObject);
                KEYED_DOMAIN_OBJECT originalDomainObject = m_StorageManager.Read(domainObject.Key);
                AssertDomainObjectNotNull(originalDomainObject);
                DispatchUpdatingEvent(originalDomainObject, domainObject);
                m_StorageManager.Update(domainObject);
                DispatchUpdatedEvent(originalDomainObject, domainObject);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }
        }

        #endregion

        #region IReadOnlyRepository Implementation

        public IRepositoryInfoProvider InfoProvider
        {
            get
            {
                IRepositoryInfoProvider thisAsInfoProvider = this as IRepositoryInfoProvider;
                return thisAsInfoProvider;
            }
        }

        public IDictionary<KEY, KEYED_DOMAIN_OBJECT> ConvertToDictionary(ICollection<KEYED_DOMAIN_OBJECT> domainObjects)
        {
            IDictionary<KEY, KEYED_DOMAIN_OBJECT> dictionary = null;

            try
            {
                dictionary = CollectionUtilities.ConvertCollectionOfKeyedObjects<KEY, KEYED_DOMAIN_OBJECT>(domainObjects);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return dictionary;
        }

        public int Count()
        {
            int count = 0;

            try
            {
                count = m_StorageManager.Count();
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return count;
        }

        public bool Exists(KEY key)
        {
            bool exists = false;

            try
            {
                exists = m_StorageManager.Exists(key);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return exists;
        }

        public bool Exists(ICollection<KEY> keys)
        {
            bool exists = false;

            try
            {
                exists = m_StorageManager.Exists(keys);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return exists;
        }

        public ICollection<KEY> FindKeys()
        {
            ICollection<KEY> keys = null;

            try
            {
                keys = m_StorageManager.FindKeys();
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return keys;
        }

        public ICollection<KEY> FindKeys(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            ICollection<KEY> keys = null;

            try
            {
                keys = m_StorageManager.FindKeys(query);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return keys;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read()
        {
            ICollection<KEYED_DOMAIN_OBJECT> domainObjects = null;

            try
            {
                domainObjects = m_StorageManager.Read();
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return domainObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            ICollection<KEYED_DOMAIN_OBJECT> domainObjects = null;

            try
            {
                domainObjects = m_StorageManager.Read(query);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return domainObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(ICollection<KEY> keys)
        {
            ICollection<KEYED_DOMAIN_OBJECT> domainObjects = null;

            try
            {
                if (!m_StorageManager.Exists(keys))
                { throw new ApplicationException("Can only call \"IRepository.Read(ICollection<KEY> keys)\" if all keys provided exist."); }

                domainObjects = m_StorageManager.Read(keys);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return domainObjects;
        }

        public KEYED_DOMAIN_OBJECT Read(KEY key)
        {
            KEYED_DOMAIN_OBJECT domainObject = default(KEYED_DOMAIN_OBJECT);

            try
            {
                if (!m_StorageManager.Exists(key))
                { throw new ApplicationException("Can only call \"IRepository.Read(ICollection<KEY> keys)\" if key provided exists."); }

                domainObject = m_StorageManager.Read(key);
            }
            catch (Exception exception)
            {
                StackTrace stackTrace = new StackTrace();
                GenerateObjectManagementException(exception, stackTrace);
            }

            return domainObject;
        }

        #endregion
    }
}